package com.hl.util.KL.Encryption.ECC;

import java.math.BigInteger;
import java.util.Vector;

import android.util.Base64;

class EccCalc {
	private BigIntegerExtend bigIntegerExtend = null;
	private StringExtend stringExtend = null;

	EccCalc() {
		bigIntegerExtend = new BigIntegerExtend();
		stringExtend = new StringExtend();
	}

	String GamalDecrypt(String cipherText, BigInteger prime, BigInteger a,
			BigInteger b, BigInteger x) {
		int t, i;
		byte[] bTemp;
		byte b1;
		String temp, temp1;
		BigPoint pTemp1, pTemp2, pTemp3, pTemp4;

		bTemp = prime.toByteArray();
		t = bTemp.length;
		// temp =
		// KL.LZO.LZOCompressor.Decompress(cipherText).DecodeBase64String();
		bTemp = Base64.decode(cipherText, Base64.NO_WRAP);
		temp = "";
		for (byte by : bTemp) {
			temp += (char) by;
		}
		String decipherText = "";
		i = temp.length();

		while (temp != null && temp.length() > 0) {
			b1 = (byte) temp.charAt(0);
			if (b1 == 0)
				i = 1;
			if (b1 == 4)
				i = 2 * t + 1;
			if (b1 == 2 || b1 == 3)
				i = t + 1;
			temp1 = temp.substring(0, i);
			temp = temp.substring(i);
			pTemp1 = StringToPoint(temp1, prime, a, b);

			b1 = (byte) temp.charAt(0);
			if (b1 == 0)
				i = 1;
			if (b1 == 4)
				i = 2 * t + 1;
			if (b1 == 2 || b1 == 3)
				i = t + 1;
			temp1 = temp.substring(0, i);
			temp = temp.substring(i);
			pTemp2 = StringToPoint(temp1, prime, a, b);

			pTemp3 = GetPointKeyMultiple(pTemp2, prime, a, x);

			pTemp2 = GetPointInverse(pTemp3, prime);
			pTemp4 = AddPoints(pTemp1, pTemp2, prime, a);
			temp1 = ExtractInbeddedString(pTemp4);
			decipherText = decipherText + temp1;
		}
		return stringExtend.DecodeBase64String(decipherText);
	}

	String ExtractInbeddedString(BigPoint p) {
		byte b;
		String inBedString = bigIntegerExtend.ToBase256String(p.X);
		b = (byte) inBedString.charAt(0);
		inBedString = inBedString.substring(1);
		inBedString = inBedString.substring(0, inBedString.length() - b);
		return inBedString;
	}

	BigPoint GetPointInverse(BigPoint p, BigInteger prime) {
		if (p.Infinity) {
			return p;
		}
		BigPoint inv = new BigPoint();
		inv.Infinity = false;

		inv.X = p.X;
		inv.Y = prime.subtract(p.Y);

		return inv;
	}

	String GamalEncrypt(String decipherText, BigInteger prime, BigInteger a,
			BigInteger b, BigInteger k, BigPoint g, BigPoint y,
			Boolean compression) {
		int t, c;
		byte[] bTemp;
		String temp, temp1;
		BigPoint pTemp1 = new BigPoint(), pTemp2, pTemp3, pTemp4;
		pTemp1.Infinity = true;
		BigInteger k1, kt;

		bTemp = prime.toByteArray();
		t = bTemp.length - 3;
		temp = stringExtend.EncodeBase64String(decipherText);
		k1 = k;
		kt = GetPrngRandom(k1);
		Boolean isOK = false;
		Vector<Byte> cipherByte = new Vector<Byte>();
		Byte[] BTemp = null;
		while (temp != null && temp.length() > 0) {
			c = 0;
			isOK = false;
			while (!isOK) {
				temp1 = temp.substring(0, Math.min(t - c, temp.length()));
				isOK = InbedStringOnEC(temp1, prime, a, b, pTemp1);
				if (isOK)
					break;
				pTemp1.Infinity = true;
				++c;
			}
			temp = temp.substring(Math.min(t - c, temp.length()));
			pTemp2 = GetPointKeyMultiple(y, prime, a, k1);
			pTemp3 = GetPointKeyMultiple(g, prime, a, k1);
			pTemp4 = AddPoints(pTemp1, pTemp2, prime, a);
			BTemp = PointToByte(pTemp4, prime, compression);
			for (Byte b1 : BTemp) {
				cipherByte.add(b1);
			}
			BTemp = PointToByte(pTemp3, prime, compression);
			for (Byte b2 : BTemp) {
				cipherByte.add(b2);
			}
			// temp1 = PointToString(pTemp4, prime, compression);
			// temp2 = PointToString(pTemp3, prime, compression);
			// cipherText = cipherText + temp1 + temp2;
			if (temp != null && temp.length() > 0) {
				kt = GetPrngRandom(kt);
				k1 = k1.modPow(kt, prime);
			}
		}
		// return
		// KL.LZO.LZOCompressor.Compress(cipherText.EncodeBase64String());
		// return Convert.ToBase64String(cipherByte.ToArray());
		BTemp = new Byte[cipherByte.size()];
		BTemp = cipherByte.toArray(BTemp);
		bTemp = new byte[BTemp.length];
		for (int i = 0; i < BTemp.length; i++) {
			bTemp[i] = BTemp[i];
		}
		return Base64.encodeToString(bTemp, Base64.NO_WRAP);
	}

	Boolean InbedStringOnEC(String inString, BigInteger prime, BigInteger a,
			BigInteger b, BigPoint p) {
		byte[] bTemp = prime.toByteArray();
		String temp = "";
		byte pad;
		int l = 0;
		BigInteger limit = BigInteger.ONE, counter, temp1, temp2, temp3, ySquare = BigInteger.ZERO;
		boolean isOK = false;
		temp = bigIntegerExtend.ToBase256String(prime);
		if (bTemp.length >= inString.length() + 3) {
			pad = (byte) Math.min(255, bTemp.length - inString.length() - 2);
			temp = (char) pad + inString;
			for (byte i = 0; i < pad; ++i) {
				temp += (char) 0;
			}
			p.X = stringExtend.Base256ToBigIntger(temp);
			temp = "1";
			for (byte i = 0; i < pad; ++i) {
				temp += "00000000";
			}
			limit = stringExtend.ByteToBigInteger(temp);
			counter = BigInteger.ZERO;
			while (!isOK && counter.abs().compareTo(limit.abs()) < 0) {
				counter = counter.add(BigInteger.ONE);
				temp1 = bigIntegerExtend.Mod(p.X.pow(2), prime);
				temp2 = bigIntegerExtend.Mod(p.X.multiply(temp1), prime);
				temp1 = bigIntegerExtend.Mod(a.multiply(p.X), prime);
				temp3 = bigIntegerExtend.Mod(temp1.add(temp2), prime);
				ySquare = bigIntegerExtend.Mod(temp3.add(b), prime);
				l = LegendreSymbol(ySquare, prime);
				if (l == 1)
					isOK = true;
				else
					p.X = p.X.add(BigInteger.ONE);
			}
			if (isOK) {
				p.Y = GetSquareRootModP(ySquare, prime);
				p.Infinity = false;
			}
		}
		return isOK;
	}

	BigInteger GetPrngRandom(BigInteger k) {
		return bigIntegerExtend.Mod(k
				.multiply(new BigInteger("44485709377909")), new BigInteger(
				"281474976710656"));
	}

	BigPoint GetPointKeyMultiple(BigPoint g, BigInteger prime, BigInteger a,
			BigInteger priK) {
		BigPoint puK = new BigPoint();
		puK.X = BigInteger.ZERO;
		puK.Y = BigInteger.ZERO;
		puK.Infinity = true;
		String temp = bigIntegerExtend.ToByteString(priK);
		char c;
		for (int i = 0; i < temp.length(); ++i) {
			c = temp.charAt(i);
			if (c == '1') {
				puK = AddPoints(puK, g, prime, a);
			}
			if (i + 1 < temp.length()) {
				puK = DoublePoint(puK, prime, a);
			}
		}
		return puK;
	}

	Byte[] PointToByte(BigPoint g, BigInteger prime, Boolean compression) {
		if (g.Infinity) {
			return new Byte[0];
		}
		int l = prime.toByteArray().length;
		Vector<Byte> bytes = new Vector<Byte>();
		byte[] tmpByte = g.X.toByteArray();
		for (byte b : tmpByte) {
			bytes.add(b);
		}
		while (bytes.size() < l) {
			bytes.add(0, (byte) 0);
		}
		if (compression) {
			bytes.add(0, (byte) ((bigIntegerExtend.IsEven(g.Y) ? 0 : 1) + 2));
		} else {
			Vector<Byte> temp = new Vector<Byte>();
			tmpByte = g.Y.toByteArray();
			for (Byte by : tmpByte) {
				temp.add(by);
			}
			while (temp.size() < l) {
				temp.add(0, (byte) 0);
			}
			bytes.add(0, (byte) 4);
			bytes.addAll(temp);
		}
		Byte[] bytes2 = new Byte[bytes.size()];
		return bytes.toArray(bytes2);
	}

	String PointToString(BigPoint g, BigInteger prime, Boolean compression) {
		if (g.Infinity) {
			return "";
		}
		String temp = "";
		int l = prime.toByteArray().length;
		String str = bigIntegerExtend.ToBase256String(g.X);
		while (str.length() < l) {
			str = (char) 0 + str;
		}
		if (compression) {
			str = (char) (bigIntegerExtend.Mod(g.Y, BigInteger.valueOf(2))
					.intValue() + 2) + str;
		} else {
			temp = bigIntegerExtend.ToBase256String(g.Y);
			while (temp.length() < l) {
				temp = (char) 0 + temp;
			}
			str = (char) 4 + str + temp;
		}
		return str;
	}

	BigPoint StringToPoint(String k, BigInteger prime, BigInteger a,
			BigInteger b) {
		if (k == null || k.length() == 0) {
			BigPoint bPoint = new BigPoint();
			bPoint.X = BigInteger.ZERO;
			bPoint.Y = BigInteger.ZERO;
			bPoint.Infinity = true;
			return bPoint;
		} else {
			BigPoint puK = new BigPoint();
			puK.Infinity = false;
			String temp = "";
			int l = prime.toByteArray().length;
			if (k.charAt(0) == (char) 4) {
				k = k.substring(1);
				temp = k.substring(0, l);
				puK.X = stringExtend.Base256ToBigIntger(temp);
				temp = k.substring(l, l + l);
				puK.Y = stringExtend.Base256ToBigIntger(temp);
			} else {
				temp = k.substring(1, l + 1);
				puK.X = stringExtend.Base256ToBigIntger(temp);
				BigInteger temp1 = bigIntegerExtend.Mod(puK.X.pow(2), prime);
				BigInteger temp2 = bigIntegerExtend.Mod(temp1.multiply(puK.X),
						prime);

				temp1 = bigIntegerExtend.Mod(puK.X.multiply(a), prime);

				BigInteger temp3 = bigIntegerExtend
						.Mod(temp1.add(temp2), prime);

				temp1 = bigIntegerExtend.Mod(temp3.add(b), prime);

				temp2 = GetSquareRootModP(temp1, prime);
				if (bigIntegerExtend.Mod(temp2, BigInteger.valueOf(2))
						.intValue() + 2 == (int) k.charAt(0)) {
					puK.Y = temp2;
				} else {
					puK.Y = prime.subtract(temp2);
				}
			}
			return puK;
		}
	}

	BigInteger GetSquareRootModP(BigInteger square, BigInteger p) {
		BigInteger n = BigInteger.valueOf(10), b, s, r, temp, temp1, temp2;
		int a = 0, l;
		l = LegendreSymbol(n, p);
		while (l != -1) {
			n.add(BigInteger.ONE);
			l = LegendreSymbol(n, p);
		}
		s = p;
		s = s.subtract(BigInteger.ONE);
		while (bigIntegerExtend.IsEven(s)) {
			s = s.shiftRight(1);
			++a;
		}
		b = n.modPow(s, p);
		temp = s.add(BigInteger.ONE);
		temp = temp.shiftRight(1);
		r = square.modPow(temp, p);
		temp1 = bigIntegerExtend.GetModeInv(square, p);
		for (int i = 0; i <= a - 2; ++i) {
			temp2 = bigIntegerExtend.Mod(r.pow(2), p);
			temp = bigIntegerExtend.Mod(temp1.multiply(temp2), p);
			for (int j = 1; j <= (a - i - 2); ++j) {
				temp2 = bigIntegerExtend.Mod(temp.pow(2), p);
				temp = temp2;
			}
			if (temp.abs().compareTo(BigInteger.ONE) != 0) {
				r = bigIntegerExtend.Mod(r.multiply(b), p);
			}
			if (i == a - 2) {
				break;
			}
			b = bigIntegerExtend.Mod(b.pow(2), p);
		}

		return r;
	}

	Integer LegendreSymbol(BigInteger a, BigInteger p) {
		BigInteger temp1, temp2, temp3, temp4, temp5;
		int l = 0;
		int i = 0;
		temp1 = bigIntegerExtend.Mod(a, p);
		if (temp1.compareTo(BigInteger.ZERO) == 0) {
			l = 0;
		} else {
			temp1 = p;
			temp2 = a;
			l = 1;
			while (temp2.abs().compareTo(BigInteger.ONE) != 0) {
				if (bigIntegerExtend.IsEven(temp2)) {
					temp3 = temp1.pow(2);
					temp4 = temp3.subtract(BigInteger.ONE);
					i = temp4.mod(BigInteger.valueOf(8)).intValue();
					temp3 = temp4.subtract(BigInteger.valueOf(i)).divide(
							BigInteger.valueOf(8));
					if (!bigIntegerExtend.IsEven(temp3)) {
						l = l * (-1);
					}
					i = temp2.mod(BigInteger.valueOf(2)).intValue();
					temp2 = temp2.subtract(BigInteger.valueOf(i)).divide(
							BigInteger.valueOf(2));
				} else {
					temp3 = temp1.subtract(BigInteger.ONE);
					temp4 = temp2.subtract(BigInteger.ONE);
					temp5 = temp3.multiply(temp4);
					i = temp5.mod(BigInteger.valueOf(4)).intValue();
					temp3 = temp5.subtract(BigInteger.valueOf(i)).divide(
							BigInteger.valueOf(4));
					if (!bigIntegerExtend.IsEven(temp3)) {
						l = l * (-1);
					}
					temp3 = bigIntegerExtend.Mod(temp1, temp2);
					temp1 = temp2;
					temp2 = temp3;
				}
			}
		}
		return l;
	}

	BigPoint AddPoints(BigPoint puK, BigPoint g, BigInteger prime, BigInteger a) {
		if (puK.Infinity) {
			return g;
		}
		if (g.Infinity) {
			return puK;
		}
		if (puK.X.abs().compareTo(g.X.abs()) == 0) {
			if (puK.Y.abs().compareTo(g.Y.abs()) == 0) {
				return DoublePoint(puK, prime, a);
			} else {
				BigPoint bPoint = new BigPoint();
				bPoint.X = BigInteger.ZERO;
				bPoint.Y = BigInteger.ZERO;
				bPoint.Infinity = true;
				return bPoint;
			}
		} else {
			BigPoint tpuK = new BigPoint();

			BigInteger temp2 = puK.Y;
			temp2 = BigInteger.ZERO.subtract(temp2);
			BigInteger temp3 = bigIntegerExtend.Mod(g.Y.add(temp2), prime);

			temp2 = puK.X;
			temp2 = BigInteger.ZERO.subtract(temp2);
			BigInteger temp1 = bigIntegerExtend.Mod(g.X.add(temp2), prime);

			temp2 = bigIntegerExtend.GetModeInv(temp1, prime);

			temp1 = bigIntegerExtend.Mod(temp3.multiply(temp2), prime);

			BigInteger temp4 = bigIntegerExtend.Mod(temp1.pow(2), prime);

			temp2 = puK.X;
			temp2 = BigInteger.ZERO.subtract(temp2);
			temp3 = bigIntegerExtend.Mod(temp4.add(temp2), prime);

			temp2 = g.X;
			temp2 = BigInteger.ZERO.subtract(temp2);

			tpuK.X = bigIntegerExtend.Mod(temp3.add(temp2), prime);

			temp2 = tpuK.X;
			temp2 = BigInteger.ZERO.subtract(temp2);
			temp3 = bigIntegerExtend.Mod(puK.X.add(temp2), prime);

			temp2 = bigIntegerExtend.Mod(temp1.multiply(temp3), prime);

			temp3 = puK.Y;
			temp3 = BigInteger.ZERO.subtract(temp3);
			tpuK.Y = bigIntegerExtend.Mod(temp2.add(temp3), prime);

			tpuK.Infinity = false;
			return tpuK;
		}
	}

	BigPoint DoublePoint(BigPoint puK, BigInteger prime, BigInteger a) {
		if (puK.Infinity) {
			return puK;
		}
		if (puK.Y.compareTo(BigInteger.ZERO) == 0) {
			BigPoint bPoint = new BigPoint();
			bPoint.X = BigInteger.ZERO;
			bPoint.Y = BigInteger.ZERO;
			bPoint.Infinity = true;
			return bPoint;
		}
		BigPoint tpuK = new BigPoint();
		tpuK.Infinity = false;

		BigInteger temp1 = bigIntegerExtend.Mod(puK.X.pow(2), prime);
		BigInteger temp2 = bigIntegerExtend.Mod(temp1.add(temp1), prime);

		BigInteger temp3 = bigIntegerExtend.Mod(temp1.add(temp2), prime);

		temp2 = bigIntegerExtend.Mod(temp3.add(a), prime);

		temp1 = bigIntegerExtend.Mod(puK.Y.add(puK.Y), prime);

		temp3 = bigIntegerExtend.GetModeInv(temp1, prime);

		temp1 = bigIntegerExtend.Mod(temp3.multiply(temp2), prime);

		temp2 = bigIntegerExtend.Mod(puK.X.add(puK.X), prime);

		temp3 = bigIntegerExtend.Mod(temp1.pow(2), prime);

		temp2 = BigInteger.ZERO.subtract(temp2);

		tpuK.X = bigIntegerExtend.Mod(temp3.add(temp2), prime);

		temp2 = tpuK.X;
		temp2 = BigInteger.ZERO.subtract(temp2);
		temp3 = bigIntegerExtend.Mod(puK.X.add(temp2), prime);

		temp2 = bigIntegerExtend.Mod(temp3.multiply(temp1), prime);

		temp1 = puK.Y;
		temp1 = BigInteger.ZERO.subtract(temp1);
		tpuK.Y = bigIntegerExtend.Mod(temp2.add(temp1), prime);

		return tpuK;
	}
}
